home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / LOGGER.PY < prev    next >
Encoding:
Python Source  |  2000-06-02  |  8.2 KB  |  267 lines

  1. # -*- Mode: Python; tab-width: 4 -*-
  2.  
  3. import asynchat
  4. import socket
  5. import string
  6. import time         # these three are for the rotating logger
  7. import os           # |
  8. import stat         # v
  9.  
  10. #
  11. # three types of log:
  12. # 1) file
  13. #    with optional flushing.  Also, one that rotates the log.
  14. # 2) socket
  15. #    dump output directly to a socket connection. [how do we
  16. #    keep it open?]
  17. # 3) syslog
  18. #    log to syslog via tcp.  this is a per-line protocol.
  19. #
  20.  
  21. #
  22. # The 'standard' interface to a logging object is simply
  23. # log_object.log (message)
  24. #
  25.  
  26. # a file-like object that captures output, and
  27. # makes sure to flush it always...  this could
  28. # be connected to:
  29. #  o    stdio file
  30. #  o    low-level file
  31. #  o    socket channel
  32. #  o    syslog output...
  33.  
  34. class file_logger:
  35.             
  36.     # pass this either a path or a file object.
  37.     def __init__ (self, file, flush=1, mode='a'):
  38.         if type(file) == type(''):
  39.             if (file == '-'):
  40.                 import sys
  41.                 self.file = sys.stdout
  42.             else:
  43.                 self.file = open (file, mode)
  44.         else:
  45.             self.file = file
  46.         self.do_flush = flush
  47.  
  48.     def __repr__ (self):
  49.         return '<file logger: %s>' % self.file
  50.  
  51.     def write (self, data):
  52.         self.file.write (data)
  53.         self.maybe_flush()
  54.         
  55.     def writeline (self, line):
  56.         self.file.writeline (line)
  57.         self.maybe_flush()
  58.         
  59.     def writelines (self, lines):
  60.         self.file.writelines (lines)
  61.         self.maybe_flush()
  62.  
  63.     def maybe_flush (self):
  64.         if self.do_flush:
  65.             self.file.flush()
  66.  
  67.     def flush (self):
  68.         self.file.flush()
  69.  
  70.     def softspace (self, *args):
  71.         pass
  72.  
  73.     def log (self, message):
  74.         if message[-1] not in ('\r', '\n'):
  75.             self.write (message + '\n')
  76.         else:
  77.             self.write (message)
  78.  
  79. # like a file_logger, but it must be attached to a filename.
  80. # When the log gets too full, or a certain time has passed,
  81. # it backs up the log and starts a new one.  Note that backing
  82. # up the log is done via "mv" because anything else (cp, gzip)
  83. # would take time, during which medusa would do nothing else.
  84.  
  85. class rotating_file_logger (file_logger):
  86.             
  87.     # If freq is non-None we back up "daily", "weekly", or "monthly".
  88.     # Else if maxsize is non-None we back up whenever the log gets
  89.     # to big.  If both are None we never back up.
  90.     def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
  91.         self.filename = file
  92.         self.mode = mode
  93.         self.file = open (file, mode)
  94.         self.freq = freq
  95.         self.maxsize = maxsize
  96.         self.rotate_when = self.next_backup(self.freq)
  97.         self.do_flush = flush
  98.  
  99.     def __repr__ (self):
  100.         return '<rotating-file logger: %s>' % self.file
  101.  
  102.     # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
  103.     def next_backup (self, freq):
  104.         (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
  105.         if freq == 'daily':
  106.             return time.mktime(yr,mo,day+1, 0,0,0, 0,0,-1)
  107.         elif freq == 'weekly':
  108.             return time.mktime(yr,mo,day-wd+7, 0,0,0, 0,0,-1)  # wd(monday)==0
  109.         elif freq == 'monthly':
  110.             return time.mktime(yr,mo+1,1, 0,0,0, 0,0,-1)
  111.         else:
  112.             return None                  # not a date-based backup
  113.  
  114.     def maybe_flush (self):              # rotate first if necessary
  115.         self.maybe_rotate()
  116.         if self.do_flush:                # from file_logger()
  117.             self.file.flush()
  118.  
  119.     def maybe_rotate (self):
  120.         if self.freq and time.time() > self.rotate_when:
  121.             self.rotate()
  122.             self.rotate_when = self.next_backup(self.freq)
  123.         elif self.maxsize:               # rotate when we get too big
  124.             try:
  125.                 if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
  126.                     self.rotate()
  127.             except os.error:             # file not found, probably
  128.                 self.rotate()            # will create a new file
  129.  
  130.     def rotate (self):
  131.         (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
  132.         try:
  133.             self.file.close()
  134.             newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
  135.             try:
  136.                 open(newname, "r").close()      # check if file exists
  137.                 newname = newname + "-%02d%02d%02d" % (hr, min, sec)
  138.             except:                             # YEARMODY is unique
  139.                 pass
  140.             os.rename(self.filename, newname)
  141.             self.file = open(self.filename, self.mode)
  142.         except:
  143.             pass
  144.  
  145. # syslog is a line-oriented log protocol - this class would be
  146. # appropriate for FTP or HTTP logs, but not for dumping stderr to.
  147.  
  148. # TODO: a simple safety wrapper that will ensure that the line sent
  149. # to syslog is reasonable.
  150.  
  151. # TODO: async version of syslog_client: now, log entries use blocking
  152. # send()
  153.  
  154. import m_syslog
  155. syslog_logger = m_syslog.syslog_client
  156.  
  157. class syslog_logger (m_syslog.syslog_client):
  158.  
  159.     svc_name='medusa'
  160.     pid_str =str(os.getpid())
  161.  
  162.     def __init__ (self, address, facility='user'):
  163.         m_syslog.syslog_client.__init__ (self, address)
  164.         self.facility = m_syslog.facility_names[facility]
  165.         self.address=address
  166.  
  167.     def __repr__ (self):
  168.         return '<syslog logger address=%s>' % (repr(self.address))
  169.  
  170.     def log (self, message):
  171.         m_syslog.syslog_client.log (
  172.             self,
  173.             '%s[%s]: %s' % (self.svc_name, self.pid_str, message),
  174.             facility=self.facility,
  175.             priority=m_syslog.LOG_INFO
  176.             )
  177.  
  178. # log to a stream socket, asynchronously
  179.  
  180. class socket_logger (asynchat.async_chat):
  181.  
  182.     def __init__ (self, address):
  183.  
  184.         if type(address) == type(''):
  185.             self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
  186.         else:
  187.             self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
  188.  
  189.         self.connect (address)
  190.         self.address = address
  191.         
  192.     def __repr__ (self):
  193.         return '<socket logger: address=%s>' % (self.address)
  194.  
  195.     def log (self, message):
  196.         if message[-2:] != '\r\n':
  197.             self.socket.push (message + '\r\n')
  198.         else:
  199.             self.socket.push (message)
  200.  
  201. # log to multiple places
  202. class multi_logger:
  203.     def __init__ (self, loggers):
  204.         self.loggers = loggers
  205.  
  206.     def __repr__ (self):
  207.         return '<multi logger: %s>' % (repr(self.loggers))
  208.  
  209.     def log (self, message):
  210.         for logger in self.loggers:
  211.             logger.log (message)
  212.  
  213. class resolving_logger:
  214.     """Feed (ip, message) combinations into this logger to get a
  215.     resolved hostname in front of the message.  The message will not
  216.     be logged until the PTR request finishes (or fails)."""
  217.  
  218.     def __init__ (self, resolver, logger):
  219.         self.resolver = resolver
  220.         self.logger = logger
  221.  
  222.     class logger_thunk:
  223.         def __init__ (self, message, logger):
  224.             self.message = message
  225.             self.logger = logger
  226.  
  227.         def __call__ (self, host, ttl, answer):
  228.             if not answer:
  229.                 answer = host
  230.             self.logger.log ('%s:%s' % (answer, self.message))
  231.  
  232.     def log (self, ip, message):
  233.         self.resolver.resolve_ptr (
  234.             ip,
  235.             self.logger_thunk (
  236.                 message,
  237.                 self.logger
  238.                 )
  239.             )
  240.  
  241. class unresolving_logger:
  242.     "Just in case you don't want to resolve"
  243.     def __init__ (self, logger):
  244.         self.logger = logger
  245.  
  246.     def log (self, ip, message):
  247.         self.logger.log ('%s:%s' % (ip, message))
  248.  
  249.  
  250. def strip_eol (line):
  251.     while line and line[-1] in '\r\n':
  252.         line = line[:-1]
  253.     return line
  254.  
  255. class tail_logger:
  256.     "Keep track of the last <size> log messages"
  257.     def __init__ (self, logger, size=500):
  258.         self.size = size
  259.         self.logger = logger
  260.         self.messages = []
  261.  
  262.     def log (self, message):
  263.         self.messages.append (strip_eol (message))
  264.         if len (self.messages) > self.size:
  265.             del self.messages[0]
  266.         self.logger.log (message)
  267.